/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package rlex.classes;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import rlex.classes.program.Command;
import rlex.classes.program.CommandBlock;
import rlex.classes.program.CommandExpression;
import rlex.classes.program.CommandIf;
import rlex.classes.program.CommandList;
import rlex.classes.program.CommandWhile;
import rlex.classes.program.Expression;
import rlex.classes.program.ExpressionAffect;
import rlex.classes.program.ExpressionFunctionCall;
import rlex.classes.program.ExpressionIdentifier;
import rlex.classes.program.ExpressionList;
import rlex.classes.program.ExpressionNumber;
import rlex.classes.program.ExpressionOperation;
import rlex.classes.program.ExpressionRelational;
import rlex.classes.program.Procedure;
import rlex.classes.program.ProcedureList;
import rlex.classes.program.Program;
import rlex.classes.program.VariableDeclaration;
import rlex.classes.program.VariableDeclarationList;

/**
 * 
 * @author Rebeca Dantas
 */
public class Syntactic {

	private List<Token> tokens = new ArrayList<Token>();
	private Token firstToken = null;
	private String wrongToken = "";

	public Syntactic(List<Token> tokens) {
		this.tokens = tokens;
		this.firstToken = this.tokens.get(0);
	}

	public Program run() {
		Program program;
		
		if ((program = checkProgram()) != null) {
			System.out.println("*** Syntatic Analisis: System ok!");
		} else {
			System.out.println("*** Syntatic Analisis: There is a wrong token: "+wrongToken);
		}
		//program.display();
		for (Token token : tokens) {
			System.out.println("Token syntatic: " + token.getName());
		}
		return program;
	}

	private void updateToken(Token token) {
		//System.out.println("remove: " + token.getName());
		this.tokens.remove(token);
		if (this.tokens != null && !this.tokens.isEmpty()) {
			this.firstToken = this.tokens.get(0);
		}
	}

	public Program checkProgram() {
		if (this.firstToken.getName().equals(Token.PROGRAM)) {
			Program program = new Program();
			updateToken(this.firstToken);
			if (this.firstToken.getType().equals(Token.IDENTIFIER)) {
				program.setName(this.firstToken.getName());
				updateToken(this.firstToken);
				if (this.firstToken.getType().equals(Token.DELIMITER)) {
					updateToken(this.firstToken);
					program.setVars(checkDeclarationVariables());
					program.setProcedures(checkDeclarationSubprogramsList());
					program.setCommand(checkCompositeCommand());
					if (this.firstToken.getName().equals(".")) {
						updateToken(this.firstToken);
						return program;
					}
				}
			}
		}
		wrongToken = this.firstToken.getName();
		return null;
	}

	private VariableDeclarationList checkDeclarationVariables() {
		VariableDeclarationList declarationlist = new VariableDeclarationList();
		if (this.firstToken.getName().equals(Token.VAR)) {
			updateToken(this.firstToken);
			declarationlist.setVars(checkListDeclarationVariables());
			return declarationlist;
		}
		wrongToken = this.firstToken.getName();		
		return null;
	}

	private List<VariableDeclaration> checkListDeclarationVariables() {
		List<VariableDeclaration> varlist = new LinkedList<VariableDeclaration>();
		
		ArrayList<String> name = checkListIdentifiers();
		if (this.firstToken.getName().equals(Token.DOUBLENET)) {
			updateToken(this.firstToken);
			String type = checkType();
			if (type != null) {
				for (int i = 0; i < name.size(); i++) {
					VariableDeclaration var = new VariableDeclaration();
					var.setName(name.get(i));
					var.setType(type);
					varlist.add(var);
				}
				if (this.firstToken.getName().equals(Token.ENDLINE)) {
					updateToken(this.firstToken);
					varlist.addAll(checkListDeclarationVariablesAux());
				}
				
				return varlist;
			} else {
				return null;
			}
			
		}
		
		return null;
	}

	private ArrayList<String> checkListIdentifiers() {
		ArrayList<String> names = new ArrayList<String>();
		
		if (this.firstToken.getType().equals(Token.IDENTIFIER)) {
			names.add(this.firstToken.getName());
			updateToken(this.firstToken);
			names.addAll(checkListIdentifiersAux());
			return names;
		}
		wrongToken = this.firstToken.getName();		
		return null;
	}

	private ArrayList<String> checkListIdentifiersAux() {
		ArrayList<String> names = new ArrayList<String>();
		
		if (this.firstToken.getName().equals(",")) {
			updateToken(this.firstToken);
			if (this.firstToken.getType().equals(Token.IDENTIFIER)) {
				names.add(this.firstToken.getName());
				updateToken(this.firstToken);
				names.addAll(checkListIdentifiersAux());
			}
		}
		
		return names;
	}

	private String checkType() {
		if (this.firstToken.getName().equals(Token.INTEGER)
				|| this.firstToken.getName().equals(Token.REAL)
				|| this.firstToken.getName().equals(Token.BOOLEAN)) {
			String type = this.firstToken.getName();
			updateToken(this.firstToken);
			return type;
		}
		wrongToken = this.firstToken.getName();		
		return null;
	}

	private List<VariableDeclaration> checkListDeclarationVariablesAux() {
		List<VariableDeclaration> varlist = new LinkedList<VariableDeclaration>();
		
		ArrayList<String> name = checkListIdentifiers();
		if (name == null) {
			return null;
		}
		if (this.firstToken.getName().equals(Token.DOUBLENET)) {
			updateToken(this.firstToken);
			String type = checkType();
			if (type != null) {
				for (int i = 0; i < name.size(); i++) {
					VariableDeclaration var = new VariableDeclaration();
					var.setName(name.get(i));
					var.setType(type);
					varlist.add(var);
				}
				if (this.firstToken.getName().equals(Token.ENDLINE)) {
					updateToken(this.firstToken);
					try {
						varlist.addAll(checkListDeclarationVariablesAux());
					} catch (Exception e) {
						return varlist;
					}
				}
				return varlist;
			} else {
				return null;
			}
		}
		return varlist;
	}

	private ProcedureList checkDeclarationSubprogramsList() {
		ProcedureList procedurelist = new ProcedureList();
		List<Procedure> procedures = new LinkedList<Procedure>();
		
		if (this.firstToken.getName().equals("procedure")) {
			do {
				if (this.firstToken.getName().equals(";")) {
					updateToken(this.firstToken);
				}
				Procedure procedure;
				if ((procedure = checkDeclarationSubprograms()) == null) {
					return null;
				}
				procedures.add(procedure);
			} while (this.firstToken.getName().equals(";"));
		}
		
		procedurelist.setProcedures(procedures);
		
		return procedurelist;
	}
	
	private Procedure checkDeclarationSubprograms() {
		Procedure procedure = new Procedure();
		
		if (this.firstToken.getName().equals("procedure")) {
			updateToken(this.firstToken);
			if(this.firstToken.getType().equals(Token.IDENTIFIER)) {
				procedure.setName(this.firstToken.getName());
				updateToken(this.firstToken);
				procedure.setParameters(checkArguments());
				if(this.firstToken.getName().equals(";")) {
					updateToken(this.firstToken);
					procedure.setVars(checkDeclarationVariables());
					ProcedureList procedurelist = new ProcedureList();
					List<Procedure> procedures = checkDeclarationSubprogramsAux();
					procedurelist.setProcedures(procedures);
					procedure.setSubprocedure(procedurelist);
					procedure.setCode(checkCompositeCommand());
					return procedure;
				} else {
					return null;
				}
			} else {
				return null;
			}
		}
		wrongToken = this.firstToken.getName();		
		return null;
	}

	private List<Procedure> checkDeclarationSubprogramsAux() {
		List<Procedure> procedures = new LinkedList<Procedure>();
		
		if (this.firstToken.getName().equals("procedure")) {
			Procedure procedure = checkDeclarationSubprograms();
			if (procedure != null) {
				procedures.add(procedure);
				if(this.firstToken.getName().equals(";")) {
					updateToken(this.firstToken);
					procedures.addAll(checkDeclarationSubprogramsAux());
				}
			} else {
				return null;
			}
			wrongToken = this.firstToken.getName();		
			return procedures;
		}
		return procedures;
	}

	private VariableDeclarationList checkArguments() {
		VariableDeclarationList varlist = new VariableDeclarationList();
		if(this.firstToken.getName().equals("(")) {
			updateToken(this.firstToken);
			varlist.setVars(checkListArguments());
			if(this.firstToken.getName().equals(")")) {
				updateToken(this.firstToken);
				return varlist;
			} else {
				return null;
			}
		}
		return null;
	}

	private List<VariableDeclaration> checkListArguments() {
		List<VariableDeclaration> varlist = new LinkedList<VariableDeclaration>();
		ArrayList<String> names = checkListIdentifiers();
		if (names != null) {
			if(this.firstToken.getName().equals(Token.DOUBLENET)) {
				updateToken(this.firstToken);
				String type = checkType();
				if (type != null) {
					for (int i = 0; i < names.size(); i++) {
						VariableDeclaration var = new VariableDeclaration();
						var.setName(names.get(i));
						var.setType(type);
						varlist.add(var);
					}
					if(this.firstToken.getName().equals(";")) {
						updateToken(this.firstToken);
						varlist.addAll(checkListArgumentsAux());
					}
					return varlist;
				}
			}
		}
		wrongToken = this.firstToken.getName();		
		return null;
	}

	private List<VariableDeclaration> checkListArgumentsAux() {
		List<VariableDeclaration> varlist = new LinkedList<VariableDeclaration>();
		ArrayList<String> names = checkListIdentifiers();
		if (names != null) {
			if(this.firstToken.getName().equals(Token.DOUBLENET)) {
				updateToken(this.firstToken);
				String type = checkType();
				if (type != null) {
					for (int i = 0; i < names.size(); i++) {
						VariableDeclaration var = new VariableDeclaration();
						var.setName(names.get(i));
						var.setType(type);
						varlist.add(var);
					}
					if(this.firstToken.getName().equals(";")) {
						updateToken(this.firstToken);
						varlist.addAll(checkListArgumentsAux());
					}
					return varlist;
				}
			}
		}
		wrongToken = this.firstToken.getName();		
		return null;
	}

	private Command checkCompositeCommand() {
		CommandBlock command = new CommandBlock();
		
		if (this.firstToken.getName().equals(Token.BEGIN)) {
			updateToken(this.firstToken);
			command.setCommands(checkListCommands());
			if (this.firstToken.getName().equals(Token.END)) {
				updateToken(this.firstToken);
				return command;
			}
		}
		wrongToken = this.firstToken.getName();		
		return null;
	}

	private CommandList checkListCommands() {
		CommandList commandlist = new CommandList();
		List<Command> commands = new LinkedList<Command>();
		
		Command command = checkCommands();
		if (command != null) {
			commands.add(command);
			commands.addAll(checkListCommandsAux());
			commandlist.setCommands(commands);
			return commandlist;
		}
		wrongToken = this.firstToken.getName();
		return null;
	}
	
	private List<Command> checkListCommandsAux() {
		List<Command> commands = new LinkedList<Command>();
		if (this.firstToken.getName().equals(";")) {
			updateToken(this.firstToken);
			Command command = checkCommands();
			if (command != null) {
				commands.add(command);
				commands.addAll(checkListCommandsAux());
			}
		}
		return commands;
	}

	private Command checkCommands() {
//		System.out.print("checkCommands\n");
		Validator validator = new Validator();
		if (this.firstToken.getType().equals(Token.IDENTIFIER)) {
			CommandExpression command = new CommandExpression();
			String identName = this.firstToken.getName();
			updateToken(this.firstToken);
			if (validator.isAllocation(this.firstToken.getName())) {
				ExpressionAffect exp = new ExpressionAffect();
				exp.setIdentifier(identName);
				updateToken(this.firstToken);
				exp.setExp(checkExpression());
				command.setExp(exp);
				return command;
			} else if (this.firstToken.getName().equals("(")) {
				updateToken(this.firstToken);
				ExpressionFunctionCall exp = new ExpressionFunctionCall();
				exp.setProcedureName(identName);
				exp.setAttribs(checkListExpressions());
				if (this.firstToken.getName().equals(")")) {
					updateToken(this.firstToken);
					command.setExp(exp);
					return command;
				}
			}
			return null;
		} else if (this.firstToken.getName().equals(Token.BEGIN)) {
			return checkCompositeCommand();
		} else if (this.firstToken.getName().equals("if")) {
			updateToken(this.firstToken);
			CommandIf command = new CommandIf();
			Expression exp = checkExpression();
			if (exp != null) {
				command.setCond(exp);
				if (this.firstToken.getName().equals("then")) {
					updateToken(this.firstToken);
					Command commandthen = checkCommands();
					if (commandthen != null) {
						command.setCommand_then(commandthen);
						command.setCommand_else(checkElse());
						return command;
					}
						
				}
			}
		} else if (this.firstToken.getName().equals("while")) {
			updateToken(this.firstToken);
			CommandWhile command = new CommandWhile();
			Expression exp = checkExpression();
			if (exp != null) {
				command.setCond(exp);
				if (this.firstToken.getName().equals("do")) {
					updateToken(this.firstToken);
					command.setCode(checkCommands());
					return command;
				}
			}
		} else if (this.firstToken.getName().equals(";")) {
			updateToken(firstToken);
			return null;
		}
		wrongToken = this.firstToken.getName();		
		return null;
	}

	private Command checkElse() {
		if (this.firstToken.getName().equals("else")) {
			updateToken(this.firstToken);
			return checkCommands();
		}
		wrongToken = this.firstToken.getName();		
		return null;
	}

	private Expression checkExpression() {
//		System.out.print("checkExpression\n");
		Expression exp = checkExpressionSimple();
		if (exp != null) {
			ExpressionRelational exprela = checkOpRelational();
			if (exprela != null) {
				exprela.setLeft(exp);
				Expression expright = checkExpressionSimple();
				exprela.setRight(expright);
//				System.out.print("End checkExpression\n");
				return exprela;
			}
//			System.out.print("End checkExpression\n");
			return exp;
		}
//		System.out.print("End checkExpression\n");
		wrongToken = this.firstToken.getName();		
		return null;
	}

	private Expression checkExpressionSimple() {
////		System.out.print("checkExpressionSimple\n");
//		if (this.firstToken.getName().equals("(")) {
//			updateToken(this.firstToken);
//			Expression exp = checkExpressionSimple();
//			if (this.firstToken.getName().equals(")")) {
//				updateToken(this.firstToken);
//				ExpressionOperation exprela = checkExpressionSimpleAux();
//				if (exprela != null) {
//					exprela.setLeft(exp);
//					Expression expright = checkExpressionSimple();
//					exprela.setRight(expright);
////					System.out.print("End checkExpressionSimple\n");
//					return exprela;
//				}
////				System.out.print("End checkExpressionSimple\n");
//				return exp;
//			}
//		} else {
			Expression exp = checkTerm();
			if (exp != null) {
				ExpressionOperation expop = checkExpressionSimpleAux();
				if (expop != null) {
					expop.setLeft(exp);
//					System.out.print("End checkExpressionSimple\n");
					return expop;
				}
//				System.out.print("End checkExpressionSimple\n");
				return exp;
			} else if (checkSignal()) {
				Expression expterm = checkTerm();
				if (expterm != null) {
					ExpressionOperation expop = checkExpressionSimpleAux();
					if (expop != null) {
						expop.setLeft(expterm);
//						System.out.print("End checkExpressionSimple\n");
						return expop;
					}
//					System.out.print("End checkExpressionSimple\n");
					return expterm;
				}
			}
//		}
//		System.out.print("End checkExpressionSimple\n");
		wrongToken = this.firstToken.getName();		
		return null;
	}

	private boolean checkSignal() {
		if (this.firstToken.getName().equals("+")
				|| this.firstToken.getName().equals("-")) {
			updateToken(this.firstToken);
			return true;
		}
		wrongToken = this.firstToken.getName();		
		return false;
	}

	private ExpressionOperation checkExpressionSimpleAux() {
		if (this.firstToken.getType().equals(Token.ADDITIVE)) {
			ExpressionOperation expop = new ExpressionOperation();
			expop.setOperator(this.firstToken.getName());
			updateToken(this.firstToken);
			Expression exp = checkTerm();
			if (exp != null) {
				ExpressionOperation exopaux = checkExpressionSimpleAux();
				if (exopaux != null) {
					exopaux.setLeft(exp);
					expop.setRight(exopaux);
				} else {
					expop.setRight(exp);
				}
				return expop;
			}
		}
		
		return null;
	}

	private Expression checkTerm() {
		Expression exp = checkFator();
		if (exp != null) {
			ExpressionOperation expaux = checkTermAux();
			if (expaux != null) {
				expaux.setLeft(exp);
				return expaux;
			}
			return exp;
		}
		wrongToken = this.firstToken.getName();  
		return null;
	}

	private ExpressionOperation checkTermAux() {
		if(this.firstToken.getType().equals(Token.MULTIPLICATIVE)) {
			ExpressionOperation expop = new ExpressionOperation();
			expop.setOperator(this.firstToken.getName());
			updateToken(firstToken);
			Expression exp = checkFator();
			
			if(exp != null) {
				ExpressionOperation expaux = checkTermAux();
				if (expaux != null) {
					expaux.setLeft(exp);
					expop.setRight(expaux);
				}
				expop.setRight(exp);
				return expop;
			}
		}
		wrongToken = this.firstToken.getName();  
		return null;
	}

//	private boolean checkOpMultiplicative() {
//		if (this.firstToken.getType().equals(Token.MULTIPLICATIVE)) {
//			updateToken(this.firstToken);
//			return true;
//		}
//		wrongToken = this.firstToken.getName();		
//		return false;
//	}

	private Expression checkFator() {
		if (this.firstToken.getType().equals(Token.IDENTIFIER)) {
			String name = this.firstToken.getName();
			updateToken(this.firstToken);
			if (this.firstToken.getName().equals("(")) {
				updateToken(this.firstToken);
				ExpressionFunctionCall exp = new ExpressionFunctionCall();
				exp.setProcedureName(name);
				exp.setAttribs(checkListExpressions());
				if (this.firstToken.getName().equals(")")) {
					updateToken(this.firstToken);
					return exp;
				}
			} else {
				ExpressionIdentifier expid = new ExpressionIdentifier();
				expid.setName(name);
				return expid;
			}
			return null;
		} else if (this.firstToken.getType().equals(Token.INTEGER)
				|| this.firstToken.getType().equals(Token.REAL)
				|| this.firstToken.getType().equals(Token.BOOLEAN)) {
			ExpressionNumber expnum = new ExpressionNumber();
			expnum.setValue(this.firstToken.getName());
			expnum.setType(this.firstToken.getType());
			updateToken(this.firstToken);
			return expnum;
		} else if(this.firstToken.getName().equals("(")) {
			updateToken(this.firstToken);
			Expression exp = checkExpression();
			if(exp!=null) {
				if(this.firstToken.getName().equals(")")) {
					updateToken(this.firstToken);
					return exp;
				}
			}
		}
		wrongToken = this.firstToken.getName();		
		return null;
	}

	private ExpressionList checkListExpressions() {
//		System.out.print("checkListExpressions\n");
		ExpressionList explist = new ExpressionList();
		List<Expression> exp = new LinkedList<Expression>();

		exp.add(checkExpression());
		exp.addAll(checkListExpressionsAux());
		
		explist.setExpressions(exp);
		return explist;
	}

	private List<Expression> checkListExpressionsAux() {
//		System.out.print("checkListExpressionsAux\n");
		List<Expression> exp = new LinkedList<Expression>();
		if (this.firstToken.getName().equals(",")) {
			updateToken(this.firstToken);
			exp.add(checkExpression());
			exp.addAll(checkListExpressionsAux());
			return exp;
		}
		return exp;
	}

	private ExpressionRelational checkOpRelational() {
		if (this.firstToken.getType().equals(Token.RELATIONAL)) {
			ExpressionRelational exp = new ExpressionRelational();
			exp.setOperator(this.firstToken.getName());
			updateToken(this.firstToken);
			return exp;
		}
		wrongToken = this.firstToken.getName();		
		return null;
	}

	
}
